package hxbase.publicapi.googlemaps;
 import GoogleMapsExtern;
 import hxbase.events.Event;

#if js
import js.Dom;
#else true
private typedef HtmlDom = Dynamic
#end

typedef Point				= GPoint
typedef Size				= GSize
typedef LatLng				= GLatLng
typedef LatLngBounds		= GLatLngBounds
typedef Overlay 			= GOverlay
typedef Copyright			= GCopyright
typedef Projection  		= GProjection
typedef Route				= GRoute
typedef Step				= GStep
typedef Bounds				= GBounds

typedef Control				= GControl
typedef SmallMapControl		= GSmallMapControl
typedef LargeMapControl		= GLargeMapControl
typedef SmallZoomControl	= GSmallZoomControl
typedef ScaleControl		= GScaleControl
typedef MapTypeControl		= GMapTypeControl
typedef OverviewMapControl	= GOverviewMapControl

typedef MapOptions = {
	var size : Size;
	var mapTypes : Array<MapType>;
	var draggableCursor : String;
	var draggingCursor : String;
}

typedef InfoWindowOptions = {
	var selectedTab : Int;
	var maxWidth : Int;
	var noCloseOnClick : Bool;
	var onOpenFn : Void -> Void;
	var onCloseFn : Void -> Void;
	var zoomLevel : Int;
	var mapType : MapType;
}

typedef MarkerOptions = {
	var icon : Icon;
	var dragCrossMove : Bool;
	var title : String;
	var clickable : Bool;
	var draggable : Bool;
	var bouncy : Bool;
	var bounceGravity : Float;
}

typedef MapTypeOptions = {
	var shortName : String;
	var urlArg : String;
	var maxResolution : Int;
	var minResolution : Int;
	var tileSize : Int;
	var textColor : String;
	var linkColor : String;
	var errorMessage : String;
	var alt : String;
}

typedef DirectionsOptions = {
	var locale : String;
	var getPolyline : Bool;
	var getSteps : Bool;
	var preserveViewport : Bool;
}

typedef MarkerManagerOptions = {
	var borderPadding : Int;
	var maxZoom : Int;
	var trackMarkers : Bool;
}

enum MapPane {
	map;
	markerShadow;
	marker;
	markerMouseTarget;
	float;
	floatShadow;
}

enum ControlAnchor {
	topRight;
	topLeft;
	bottomRight;
	bottomLeft;
}

private class GEventGroup extends EventGroup
{
	public function new(inst : EventDispatchers<Dynamic>)
	{
		super();
		var ref = this;
		
		// Autobind google events
		for(f in Reflect.fields(this)) { // Gotta love the automatic instantiation ;-)
			var e = Reflect.field(this, f);
			// If field has the bind function, add a listener.
			if(e.bind) GEvent.addListener(inst, f, untyped function(){ e.call.apply(ref, arguments); });
		}
	}
}

class MarkerManagerEvents extends GEventGroup {
	var changed : Event<Bounds->Int>;
}

class CopyrightEvent extends GEventGroup {
	var newcopyright : Event<Copyright->Void>;
}

class InfoWindowEvent extends GEventGroup {
	var closeclick : Event<Void -> Void>;
}

class RemoveEvent extends GEventGroup {
	var remove : Event<Void->Void>;
}

class MarkerEvents extends RemoveEvent
{
	var click : Event<Void->Void>;
	var dblclick : Event<Void->Void>;
	var mousedown : Event<Void->Void>;
	var mouseup : Event<Void->Void>;
	var mouseover : Event<Void->Void>;
	var mouseout : Event<Void->Void>;
	var infowindowopen : Event<Void->Void>;
	var infowindowbeforeclose : Event<Void->Void>;
	var infowindowclose : Event<Void->Void>;
	var dragstart : Event<Void->Void>;
	var drag : Event<Void->Void>;
	var dragend : Event<Void->Void>;
	var visibilitychanged : Event<Bool->Void>;
}

class DirectionsEvents extends GEventGroup
{
	var load : Event<Void->Void>;
	var addoverlay : Event<Void->Void>;
	var error : Event<Void->Void>;
}

class Map2Events extends GEventGroup
{
	var addmaptype				: Event< MapType -> Void >;
	var removemaptype			: Event< MapType -> Void >;
	var click					: Event< Overlay -> Point -> Void >;
	var dblclick				: Event< Overlay -> Point -> Void >;
	var singlerightclick		: Event< Point -> Dynamic -> Overlay -> Void >;
	var movestart				: Event< Void -> Void >;
	var move					: Event< Void -> Void >;
	var moveend					: Event< Void -> Void >;
	var zoomend					: Event< Int -> Int -> Void >;
	var maptypechanged			: Event< Void -> Void >;
	var infowindowopen			: Event< Void -> Void >;
	var infowindowbeforeclose	: Event< Void -> Void >;
	var infowindowclose			: Event< Void -> Void >;
	var addoverlay				: Event< Overlay -> Void >;
	var removeoverlay			: Event< Overlay -> Void >;
	var clearoverlays			: Event< Void -> Void >;
	var mouseover				: Event< LatLng -> Void >;
	var mouseout				: Event< LatLng -> Void >;
	var mousemove				: Event< LatLng -> Void >;
	var dragstart				: Event< Void -> Void >;
	var drag					: Event< Void -> Void >;
	var dragend					: Event< Void -> Void >;
	/** One time Event which destroys itself after call. **/
	var load					: Event< Void -> Void >;
	
	public function new( inst : Map2 )
	{
		super( inst );
		var ref = this;
		// Add onload selfdestruct handler
		load.bind( this, function(){ ref.load = null; } );
	}
}

class Map2 extends GMap2, implements EventDispatchers<Map2Events>
{
	static var initComplete = false;
	
	/** Initialize GoogleMaps events **/
	static function init() { // Wrap it up!
		untyped {	
			var p = Map2.prototype;
			p.getInfoWindowFunction = p.getInfoWindow;
			p.getInfoWindow = function() { 
				var i = this.getInfoWindowFunction();
				if(i.events == null) InfoWindow.bindEvents(i);
				return i;
			}
			p.getPaneFunction = p.getPane;
			p.getPane = p.getPaneByEnum;
			
			for(t in 0 ... G_DEFAULT_MAP_TYPES.length) 
				G_DEFAULT_MAP_TYPES[t].events = new CopyrightEvent(G_DEFAULT_MAP_TYPES[t]);
		}
	}
	
	static public function Unload() {
		untyped __js__("GUnload()");
	}
	
	public var events : Map2Events;
	
	public function new(container : HtmlDom, ?opts : MapOptions) {
		if(!initComplete) init();
		super(container, opts);
		events = new Map2Events(this);
	}
	
	function getPaneFunction(pane:GMapPane) : HtmlDom {return null;}
	
	function getPaneByEnum(pane:MapPane) : HtmlDom
	{
		return untyped switch(pane) {
			case map:				getPane(__js__("G_MAP_MAP_PANE"));
			case markerShadow:		getPane(__js__("G_MAP_MARKER_SHADOW_PANE"));
			case marker:			getPane(__js__("G_MAP_MARKER_PANE"));
			case markerMouseTarget:	getPane(__js__("G_MAP_MARKER_MOUSE_TARGET_PANE"));
			case float:				getPane(__js__("G_MAP_FLOAT_PANE"));
			case floatShadow:		getPane(__js__("G_MAP_FLOAT_SHADOW_PANE"));
		}
	}
}

class InfoWindow extends GOverlay, implements EventDispatchers<InfoWindowEvent>
{
	static function bindEvents( obj : Dynamic ) {
		obj.events = new Map2Events(obj);
	}
	
	public var events : InfoWindowEvent;
	
	public function selectTab(index:Int) : Void;
	public function hide() : Void;
	public function show() : Void;
	public function isHidden() : Bool {return null;}
	public function reset(?point:LatLng, ?tabs:Array<InfoWindowTab>, ?size:Size, ?offset:Size, ?selectedTab:Int) : Void;
	public function getPoint() : LatLng {return null;}
	public function getPixelOffset() : Size {return null;}
	public function getSelectedTab() : Int {return null;}
	public function getTabs() : Array<InfoWindowTab> {return null;}
	public function getContentContainers() : Array<HtmlDom> {return null;}
}

class InfoWindowTab extends GInfoWindowTab
{
	public function new(label : String, content : Dynamic) {
		super(label, content);
	}
}

class Marker extends GMarker, implements EventDispatchers<MarkerEvents>
{
	public function new(latlng : LatLng, ?opts : MarkerOptions) {
		super(latlng, opts);
		events = new MarkerEvents(this);
	}
	
	public var events : MarkerEvents;
}

class Polyline extends GPolyline, implements EventDispatchers<RemoveEvent>
{
	static public function fromEncoded(color:Null<String>,  weight:Null<Int>,  opacity:Null<Float>,  points:String,  zoomFactor:Int,  levels:String,  numLevels:Int) : Polyline
	{
		var r = GPolyline.fromEncoded(color,weight,opacity,points,zoomFactor,levels,numLevels);
		Reflect.setField(r, 'events', new RemoveEvent(r));
		return r;
	}
	
	public var events : RemoveEvent;
	
	public function new(points:Array<LatLng>, ?color:String, ?weight:Int, ?opacity:Float) {
		super(points,color,weight,opacity);
		events = new RemoveEvent(this);
	}
}

class Polygon extends GPolygon, implements EventDispatchers<RemoveEvent>
{
	static public function fromEncoded(polylines:Array<String>,  ?fill:Bool,  ?color:String,  ?opacity:Float,  ?outline:Bool) : Polygon
	{
		var r = GPolygon.fromEncoded(polylines,fill,color,opacity,outline);
		Reflect.setField(r, 'events', new RemoveEvent(r));
		return r;
	}
	
	public var events : RemoveEvent;
	
	public function new(points:Array<LatLng>, ?strokeColor:String, ?strokeWeight:Int, ?strokeOpacity:Float, ?fillColor:String, ?fillOpacity:Float) {
		super(points, strokeColor, strokeWeight, strokeOpacity, fillColor, fillOpacity);
		events = new RemoveEvent(this);
	}
}

class GroundOverlay extends GGroundOverlay
{
	public function new(imageUrl:String, bounds:LatLngBounds) {
		super(imageUrl,bounds);
	}
}

class Icon extends GIcon
{
	static public var DEFAULT : Icon;
	static function __init__(){ DEFAULT = GIcon.G_DEFAULT_ICON; }
	
	public function new(?copy:Icon, ?image:String) {
		super(copy,image);
	}
}

class ControlPosition extends GControlPosition {
	public function new(anchor:ControlAnchor, offset:Size) {
		super( switch(anchor) {
			case topRight:		untyped __js__("G_ANCHOR_TOP_RIGHT");
			case topLeft:		untyped __js__("G_ANCHOR_TOP_LEFT");
			case bottomRight:	untyped __js__("G_ANCHOR_BOTTOM_RIGHT");
			case bottomLeft:	untyped __js__("G_ANCHOR_BOTTOM_LEFT");
		}, offset);
	}
}

class MapType extends GMapType, implements EventDispatchers<CopyrightEvent>
{
	public static var NORMAL_MAP : MapType;
	public static var SATELLITE_MAP : MapType;
	public static var HYBRID_MAP : MapType;
	public static var DEFAULT_MAP_TYPES : Array<MapType>;
	
	static function __init__() untyped {
		NORMAL_MAP = G_NORMAL_MAP;
		SATELLITE_MAP = G_SATELLITE_MAP;
		HYBRID_MAP = G_HYBRID_MAP;
		DEFAULT_MAP_TYPES = G_DEFAULT_MAP_TYPES;
	}
	
	public var events : CopyrightEvent;
	
	public function new(layers:Array<TileLayer>, projection:Projection, name:String, ?opts:MapTypeOptions) {
		super(layers,projection,name,opts);
		events = new CopyrightEvent(this);
	}
	
	public function getTileLayers() : Array<TileLayer>
	{
		var r = super.getTileLayers();
		for(x in r) untyped if(r.events == null) r.events == new CopyrightEvent(this);
		return r;
	}
}

class TileLayer extends GTileLayer, implements EventDispatchers<CopyrightEvent>
{
	
	public var events : CopyrightEvent;
	
	public function new(copyrights:CopyrightCollection, minResolution:Int, maxResolution:Int) {
		super(copyrights,minResolution,maxResolution);
		events = new CopyrightEvent(this);
	}
}

class CopyrightCollection extends GCopyrightCollection, implements EventDispatchers<CopyrightEvent>
{
	public var events : CopyrightEvent;
	
	public function new(prefix) {
		super(prefix);
		events = new CopyrightEvent(this);
	}
}

class Directions extends GDirections, implements EventDispatchers<DirectionsEvents>
{
	public var  events : DirectionsEvents;
	
	public function new(?map:Map2, ?panel:HtmlDom)
	{
		super(map, panel);
		events = new DirectionsEvents(this);
	}
}

class MarkerManager extends GMarkerManager, implements EventDispatchers<MarkerManagerEvents>
{
	public var events : MarkerManagerEvents;
	
	public function new(map:Map2, ?opts:MarkerManagerOptions)
	{
		super(map, opts);
		events = new MarkerManagerEvents(this);
	}
}

/*

class Copyright {}
class MapType {}
class TileLayerOverlay {}
	
class MercatorP {}

GroundOverlay

Bounds
	
Control
XmlHttp
Xml
Xslt
Log
DraggableObject
DraggableObjectOptions
GeoStatusCode
GeoAddressAccuracy
ClientGeocoder
	
GeocodeCache
FactualGeocodeCache
MarkerManager
MarkerManagerOptions
GeoXml
DownloadUrl
BrowserIsCompatible
TrafficOverlay
*/